﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;

namespace Notoric.ViewModel.Elements.CWNotation
{
    /// <summary>
    /// Represents one segment of a <see cref="Beam"/>.
    /// </summary>
    public class BeamSegment : VirtualViewModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="BeamSegment"/> instance.
        /// </summary>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Beam"/>
        /// </para>
        /// <para>
        /// The beam that directly owns this segment.
        /// </para>
        /// </param>
        /// <param name="duration">
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The duration that this segment corresponds to.
        /// </para>
        /// </param>
        /// <param name="leftSymbol">
        /// <para>
        /// Type: <see cref="DurationalSymbol"/>
        /// </para>
        /// <para>
        /// The left border symbol of the segment.
        /// </para>
        /// </param>
        /// <param name="rightSymbol">
        /// <para>
        /// Type: <see cref="DurationalSymbol"/>
        /// </para>
        /// <para>
        /// The right border symbol of the segment.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="owner"/>, <paramref name="leftSymbol"/> or <paramref name="rightSymbol"/> is <see langword="null"/>.
        /// </exception>
        public BeamSegment(Beam owner, ushort duration, DurationalSymbol leftSymbol, DurationalSymbol rightSymbol)
            : base(owner)
        {
            if (leftSymbol == null)
                throw new ArgumentNullException("leftSymbol");
            if (rightSymbol == null)
                throw new ArgumentNullException("rightSymbol");

            this.duration = duration;
            this.leftSymbol = leftSymbol;
            this.rightSymbol = rightSymbol;

            leftSymbol.PropertyChanged += new PropertyChangedEventHandler(leftSymbol_PropertyChanged);
            rightSymbol.PropertyChanged += new PropertyChangedEventHandler(rightSymbol_PropertyChanged);

            RegisterToOwnerPropertyChanged<Beam>();
            Points = new List<Point>();
            SetPoints();
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the value of some property of the left border symbol has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void leftSymbol_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Position" || e.PropertyName == "StemDirection")
                SetPoints();
        }

        /// <summary>
        /// Occurs when the value of some property of the right border symbol has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void rightSymbol_PropertyChanged(object sender, global::System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Position" || e.PropertyName == "StemDirection")
                SetPoints();
        }

        /// <inheritdoc/>
        protected override void Owner_PropertyChanged(ViewModelElement owner, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Y1" || e.PropertyName == "Y2" || e.PropertyName == "X1" || e.PropertyName == "X2")
                SetPoints();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Computes the points that define the polygon of this beam segment.
        /// </summary>
        private void SetPoints()
        {
            var parent = FindOwner<Beam>();
            var staff = FindOwner<Staff>();
            var xDistance = parent.X2 - parent.X1;
            var yDistance = parent.Y2 - parent.Y1;
            var x1 = (leftSymbol.BasicDuration <= duration ? leftSymbol.Position : rightSymbol.Position - staff.SpaceSize);
            var x2 = (rightSymbol.BasicDuration <= duration ? rightSymbol.Position : leftSymbol.Position + staff.SpaceSize);
            var t1 = (x1 - parent.X1) / xDistance;
            var t2 = (x2 - parent.X1) / xDistance;
            var level = 4 - Model.Utils.Duration.HighestBit(duration);
            double upperOffset = 0;
            double lowerOffset = parent.Stroke;
            if (leftSymbol.BasicDuration <= duration && ((Chord)leftSymbol).StemDirection == StemOrientation.Down)
            {
                level = -level;
                upperOffset = parent.Stroke;
                lowerOffset = 0;
            }
            else if (rightSymbol.BasicDuration <= duration && ((Chord)rightSymbol).StemDirection == StemOrientation.Down)
            {
                level = -level;
                upperOffset = parent.Stroke;
                lowerOffset = 0;
            }

            Points.Clear();
            Points.Add(new Point(x1, parent.Y1 + t1 * yDistance + level * (FindOwner<Staff>().SpaceSize * 0.6) + upperOffset));
            Points.Add(new Point(x2, parent.Y1 + t2 * yDistance + level * (FindOwner<Staff>().SpaceSize * 0.6) + upperOffset));
            Points.Add(new Point(x2, parent.Y1 + t2 * yDistance + level * (FindOwner<Staff>().SpaceSize * 0.6) + lowerOffset));
            Points.Add(new Point(x1, parent.Y1 + t1 * yDistance + level * (FindOwner<Staff>().SpaceSize * 0.6) + lowerOffset));

            RaisePropertyChanged("Points");
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the duration to which this segment corresponds.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The duration to which the segment corresponds.<br/>
        /// This property defines the segment level and gives it the value
        /// to which it compares the durations of its border symbols
        /// in order to determine if it is fractional or not.
        /// </para>
        /// </value>
        public ushort Duration
        {
            get { return duration; }
            set
            {
                duration = value;
            }
        }

        /// <summary>
        /// Gets or sets the left border symbol of the segment.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="DurationalSymbol"/>
        /// </para>
        /// <para>
        /// The left border symbol (the one at which it starts) of the segment.
        /// </para>
        /// </value>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="value"/> is <see langword="null"/>.
        /// </exception>
        public DurationalSymbol LeftSymbol
        {
            get { return leftSymbol; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("LeftSymbol");

                leftSymbol.PropertyChanged -= leftSymbol_PropertyChanged;
                leftSymbol = value;
                leftSymbol.PropertyChanged += new PropertyChangedEventHandler(leftSymbol_PropertyChanged);
            }
        }

        /// <summary>
        /// Gets or sets the right border symbol of the segment.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="DurationalSymbol"/>
        /// </para>
        /// <para>
        /// The right border symbol (the one at which it ends) of the segment.
        /// </para>
        /// </value>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="value"/> is <see langword="null"/>.
        /// </exception>
        public DurationalSymbol RightSymbol
        {
            get { return rightSymbol; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                rightSymbol.PropertyChanged -= rightSymbol_PropertyChanged;
                rightSymbol = value;
                rightSymbol.PropertyChanged += new PropertyChangedEventHandler(rightSymbol_PropertyChanged);
            }
        }

        /// <summary>
        /// Gets the collection of points of this segment's polygon.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="List{T}"/> of <see cref="Point"/>
        /// </para>
        /// <para>
        /// The collection of points that define the polygon of this beam segment.
        /// </para>
        /// </value>
        public List<Point> Points
        {
            get;
            private set;
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="Duration"/> property.
        /// </summary>
        private ushort duration;
        /// <summary>
        /// Holds the value of the <see cref="LeftSymbol"/> property.
        /// </summary>
        private DurationalSymbol leftSymbol;
        /// <summary>
        /// Holds the value of the <see cref="RightSymbol"/> property.
        /// </summary>
        private DurationalSymbol rightSymbol;

        #endregion
    }
}
